/*

 */

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util._

case class GrindingException(msg: String) extends Exception(msg)

case class FrothingException(msg: String) extends Exception(msg)

case class WaterBoilingException(msg: String) extends Exception(msg)

case class BrewingException(msg: String) extends Exception(msg)

case class Water(temperature: Int)

object CappuccinoFutureDemo {
  type CoffeeBeans = String
  type GroundCoffee = String
  type Milk = String
  type FrothedMilk = String
  type Espresso = String
  type Cappuccino = String

  def grind(beans: CoffeeBeans): Future[GroundCoffee] = Future {
    println("1. 研磨所需的咖啡豆...")
    Thread.sleep(Random.nextInt(2000))
    if (beans == "baked beans") throw GrindingException("are you joking?")
    println("1.1 研磨咖啡豆完成...")
    s"ground coffee of $beans"
  }

  def heatWater(water: Water): Future[Water] = Future {
    println("2. 加热一些水")
    Thread.sleep(Random.nextInt(2000))
    println("2.1 水烧开了!")
    water.copy(temperature = 85)
  }

  def frothMilk(milk: Milk): Future[FrothedMilk] = Future {
    println("4. 打奶泡!")
    Thread.sleep(Random.nextInt(2000))
    println("4.1 打奶泡已经准备好了！")
    s"frothed $milk"
  }

  def brew(coffee: GroundCoffee, heatedWater: Water): Future[Espresso] = Future {
    println("3. 用研磨好的咖啡豆和热水制做一杯咖啡")
    Thread.sleep(Random.nextInt(2000))
    println("3.1 用研磨好的咖啡豆和热水制做一杯咖啡!")
    "espresso"
  }

  def combine(espresso: Espresso, frothedMilk: FrothedMilk): Cappuccino = {
    println("5. 结合咖啡和奶泡做成卡布奇诺")
    "cappuccino"
  }

  def prepareCappuccinoSequentially(): Future[Cappuccino] =
    for {
      ground <- grind("arabica beans")
      water <- heatWater(Water(25))
      foam <- frothMilk("milk")
      espresso <- brew(ground, water)
    } yield combine(espresso, foam)

  def prepareCappuccino(): Future[Cappuccino] = {
    val groundCoffee = grind("arabica beans")
    val heatedWater = heatWater(Water(20))
    val frothedMilk = frothMilk("milk")
    for {
      ground <- groundCoffee
      water <- heatedWater
      foam <- frothedMilk
      espresso <- brew(ground, water)
    } yield combine(espresso, foam)
  }

  def main(args: Array[String]): Unit = {
    val result = prepareCappuccino()
    result.onComplete(s => {
      println(s.get)
    })
    Thread.sleep(Random.nextInt(50000))
  }
}
